/*
 *  composition 0018.cpp
 *  SoundController0002
 *
 *  Created by jiro on 11/02/22.
 *  Copyright 2011 jiro music. All rights reserved.
 *
 */

#include "composition 0018.h"
#include "../sc/Common/WellTemperament.h"
#include <AudioToolbox/AudioToolbox.h>
#include "../sc/SoundController.h"
#include "../sc/Common/Note.h"

using namespace sc;

void Composer_0018::Compose( const std::string& path )
{
	AudioRecorder32 recorder( 2, 1 );

	const double beatsPerMiniute = 100;
	const double beatNumerator = 4;
	const double beatFraction = 4;
	TimeController timeController( beatsPerMiniute, beatNumerator, beatFraction );

	Note::MidiNumber upperNotes00[] = { 
		Note::GSharp3, Note::A3, Note::E4, Note::DSharp4, Note::FSharp4, Note::GSharp4,
		Note::A4, Note::E4, Note::B3, Note::CSharp4, Note::GSharp4, Note::DSharp5,
		Note::E4, Note::DSharp4, Note::GSharp3, Note::A4, Note::FSharp4, Note::E4,
		Note::FSharp4, Note::GSharp4, Note::CSharp5, Note::A4, Note::E4, Note::B3
	};

	Note::MidiNumber upperNotes01[] = {
		Note::E4, Note::A4, Note::GSharp5, Note::GSharp5, Note::FSharp5, Note::DSharp5,
		Note::E4, Note::B4, Note::A3, Note::DSharp4, Note::GSharp4, Note::CSharp5,
		Note::GSharp5, Note::DSharp5, Note::E4, Note::E4, Note::FSharp4, Note::GSharp4, 
		Note::CSharp5, Note::GSharp4, Note::DSharp4, Note::B3, Note::E4, Note::A4
	};
	Note::MidiNumber upperLastNote = Note::E4;

	Note::MidiNumber lowerLastNote = Note::B4;

	Note::MidiNumber bassNotes00[] = {
		Note::A2, Note::FSharp2, Note::A2, Note::CSharp3, 
		Note::E3, Note::A3, Note::FSharp3, Note::A2
//		Note::A1, Note::FSharp1, Note::A1, Note::CSharp2, 
//		Note::E2, Note::A2, Note::FSharp2, Note::A1
	};
	Note::MidiNumber bassLastNote = Note::A2;

	const double quaterNoteDuration = timeController.GetNoteSeconds( TimeController::QuarterNote );
	const double halfNoteDuration = timeController.GetNoteSeconds( TimeController::HalfNote );
	const double twoBeatTripletNoteDuration = timeController.GetNoteSeconds( TimeController::HalfNote, 3 );
	const double tripletNoteDuration = timeController.GetNoteSeconds( TimeController::QuarterNote, 3 );
	const double wholeNoteDuration = timeController.GetNoteSeconds( TimeController::WholeNote );
	const double barDuration = timeController.GetNoteSeconds( TimeController::WholeNote );

	const size_t upperNoteCount = 2 * ( sizeof( upperNotes01 ) + sizeof( upperNotes00 ) ) / sizeof( Note::MidiNumber ) + 1;
	const size_t lowerNoteCount = upperNoteCount;
	const size_t bassNoteCount = 4 * sizeof( bassNotes00 ) / sizeof( Note::MidiNumber ) + 1;

	std::vector< NoteInfo > upperNoteInfos( upperNoteCount );
	std::vector< NoteInfo > lowerNoteInfos( lowerNoteCount );
	std::vector< NoteInfo > bassNoteInfos( bassNoteCount );

	//	set uppper part note number and duration
	size_t countMax = sizeof( upperNotes00 ) / sizeof( Note::MidiNumber ); 
	Note::MidiNumber* upperNotes[] = { upperNotes00, upperNotes01 };
	size_t infoIndex = 0;
	for ( int j = 0; j < 2; j++ ) {
		for ( int i = 0; i < 2; i++ ) { 
			for ( size_t index = 0; index < countMax; index++ ) {	//	set note info
				NoteInfo& info = upperNoteInfos[ infoIndex ];
				info.frequency = WellTemperament::GetFrequency( upperNotes[ j ][ index ] );
				info.duration = twoBeatTripletNoteDuration;
				infoIndex++;
			}
		}
	}
	upperNoteInfos.back().frequency = WellTemperament::GetFrequency( upperLastNote);
	upperNoteInfos.back().duration = wholeNoteDuration;

	//	set lower part note number and duration	
	lowerNoteInfos[ 0 ].frequency = 0.0;
	lowerNoteInfos[ 0 ].duration = quaterNoteDuration;
	const size_t skippedNoteCount = 3;
	infoIndex = 1;
	for ( int index = skippedNoteCount; index < countMax; index++, infoIndex++  ) {
		NoteInfo& info = lowerNoteInfos[ infoIndex ];
		info.frequency = WellTemperament::GetFrequency( upperNotes00[ index ] );
		info.duration = twoBeatTripletNoteDuration;
	}
	for ( int index = 0; index < countMax; index++, infoIndex++  ) {
		NoteInfo& info = lowerNoteInfos[ infoIndex ];
		info.frequency = WellTemperament::GetFrequency( upperNotes00[ index ] );
		info.duration = twoBeatTripletNoteDuration;
	}
	for ( int i = 0; i < 2; i++ ) {
		for ( size_t index = 0; index < countMax; index++, infoIndex++ ) {
			NoteInfo& info = lowerNoteInfos[ infoIndex ];
			info.frequency = WellTemperament::GetFrequency( upperNotes01[ index ] );
			info.duration = twoBeatTripletNoteDuration;
		}
	}
	lowerNoteInfos[ infoIndex ].frequency = WellTemperament::GetFrequency( Note::B3 );  
	lowerNoteInfos[ infoIndex ].duration = twoBeatTripletNoteDuration;
	infoIndex++;
	lowerNoteInfos[ infoIndex ].frequency = WellTemperament::GetFrequency( Note::E4 );  
	lowerNoteInfos[ infoIndex ].duration = tripletNoteDuration;
	infoIndex++;	
	lowerNoteInfos[ infoIndex ].frequency = WellTemperament::GetFrequency( lowerLastNote );  
	lowerNoteInfos[ infoIndex ].duration = wholeNoteDuration;

	//	set bass part note number and duration
	infoIndex = 0;
	int count = sizeof( bassNotes00 ) / sizeof( Note::MidiNumber );
	for ( int j = 0; j < 4; j++ ) {
		for ( int i = 0; i < count; i++, infoIndex++ ) {
			NoteInfo& info = bassNoteInfos[ infoIndex ];
			info.frequency = WellTemperament::GetFrequency( bassNotes00[ i ] );
			info.duration = halfNoteDuration;
		}
	}
	bassNoteInfos.back().frequency = WellTemperament::GetFrequency( bassLastNote );
	bassNoteInfos.back().duration = wholeNoteDuration;

	//	set dynamics
	const StraightLine pianoToMezzoPianoLine = StraightLine( 0.0, Note::piano, barDuration, Note::mezzoPiano );
	const StraightLine mezzoPianoToPianoLine = StraightLine( 0.0, Note::mezzoPiano, barDuration, Note::piano );
	const StraightLine pianoToPianissimoLine = StraightLine( 0.0, Note::piano, barDuration, Note::pianissimo );
//	const Line& pianoToMezzoPianoLine = LineFactory::Create( Line::Type_Straight, 0.0, Note::piano, barDuration, Note::mezzoPiano );
//	const Line& mezzoPianoToPianoLine = LineFactory::Create( Line::Type_Straight, 0.0, Note::mezzoPiano, barDuration, Note::piano );
//	const Line& pianoToPianissimoLine = LineFactory::Create( Line::Type_Straight, 0.0, Note::piano, barDuration, Note::pianissimo );

	enum DynamicsType {
		pianoToMezzoPiano,
		mezzoPianoToPiano,
		pianoToPianissimo,
		mezzoPiano,
		piano,
		pianissimo,
	};

	//	2 ~ 16 bar
	DynamicsType dynamicsType[] = {
		pianoToMezzoPiano,
		mezzoPiano,
		mezzoPiano,
		mezzoPianoToPiano,
		piano,
		pianoToMezzoPiano,
		mezzoPiano,
		mezzoPianoToPiano,
		pianoToMezzoPiano,
		mezzoPiano,
		mezzoPiano,
		mezzoPianoToPiano,
		piano,
		piano,
		piano,
//		piano,
		pianoToPianissimo,
	};

	//	set dynamics
	size_t upperPartIndex = 0;
	size_t lowerPartIndex = 0;
	size_t bassPartIndex = 0;
	double time = 0.0;

	//	1 bar
	for ( ; upperPartIndex < 6; upperPartIndex++ ) {
		NoteInfo& info = upperNoteInfos[ upperPartIndex ];
		info.dynamics = pianoToMezzoPianoLine.GetY( time );
		time += info.duration;
	}
	time = 0.0;
	for ( ; lowerPartIndex < 5; lowerPartIndex++ ) {
		NoteInfo& info = lowerNoteInfos[ lowerPartIndex ];
		info.dynamics = pianoToMezzoPianoLine.GetY( time );
		time += info.dynamics;
	}
	time = 0.0;
	for ( ; bassPartIndex < 2; bassPartIndex++ ) {
		NoteInfo& info = bassNoteInfos[ bassPartIndex ];
		info.dynamics = pianoToMezzoPianoLine.GetY( time );
		time += info.duration;
	}

	//	2 ~ 16 bar
	for ( int i = 1, count = sizeof ( dynamicsType ) / sizeof( DynamicsType ); i < count; i++ ) {
		const Line* line = NULL;
		switch ( dynamicsType[ i ] ) {
		case pianoToMezzoPiano:
			line = & pianoToMezzoPianoLine;
			break;
		case mezzoPianoToPiano:
			line = & mezzoPianoToPianoLine;
			break;
		case pianoToPianissimo:
			line = & pianoToPianissimoLine;
			break;
		}
		
		if ( line != NULL ) {
			time = 0.0;
			for ( int j = 0; j < 6; j++ ) {
				upperNoteInfos[ upperPartIndex ].dynamics = line->GetY( time );
				lowerNoteInfos[ lowerPartIndex ].dynamics = line->GetY( time );
				upperPartIndex++;
				lowerPartIndex++;
				time += upperNoteInfos[ upperPartIndex ].duration;
			}
			time = 0.0;
			for ( int j = 0; j < 2; j++ ) {
				bassNoteInfos[ bassPartIndex ].dynamics = line->GetY( time );
				bassPartIndex++;
				time += bassNoteInfos[ bassPartIndex ].duration;
			}
		} else {
			double dynamics;
			switch ( dynamicsType[ i ] ) {
			case mezzoPiano:
				dynamics = Note::mezzoPiano;
				break;
			case piano:
				dynamics = Note::piano;
				break;
			case pianissimo:
				dynamics = Note::pianissimo;
				break;
			}
			for ( int j = 0; j < 6; j++ ) {
				upperNoteInfos[ upperPartIndex ].dynamics = dynamics;
				lowerNoteInfos[ lowerPartIndex ].dynamics = dynamics;
				upperPartIndex++;
				lowerPartIndex++;
			}
			for ( int j = 0; j < 2; j++ ) {
				bassNoteInfos[ bassPartIndex ].dynamics = dynamics;
				bassPartIndex++;
			}
		}
	}

	//	17 bar
//	upperNoteInfos.back().dynamics = Note::piano;
//	lowerNoteInfos.back().dynamics = Note::piano;
//	bassNoteInfos.back().dynamics = Note::piano;
	upperNoteInfos.back().dynamics = Note::pianissimo;
	lowerNoteInfos.back().dynamics = Note::pianissimo;
	bassNoteInfos.back().dynamics = Note::pianissimo;

//	for ( int i = 0; i < upperNoteInfos.size(); i++ ) {
//		std::cout << i << " " << upperNoteInfos[ i ].ToString() << std::endl;
//		std::cout << i << " " << lowerNoteInfos[ i ].ToString() << std::endl;
//		std::cout << i << " " << bassNoteInfos[ i ].ToString() << std::endl;
//	}

	SoundElementFactory32::DisposeInstances();

	//	generate sound data
	std::vector< const SoundElement32* > upperPartSoundElements = SoundGenerator::Generate( upperNoteInfos );
	std::vector< const SoundElement32* > lowerPartSoundElements = SoundGenerator::Generate( lowerNoteInfos );
	std::vector< const SoundElement32* > bassPartSoundElements = SoundGenerator::Generate( bassNoteInfos );
	time = 0.0;
	for ( int index = 0, count = upperPartSoundElements.size(); index < count; index++ ) {
		const SoundElement32* element = upperPartSoundElements[ index ];
		recorder.Record( 0, 0, time, element, 0.6, 0.4 );
		time += element->GetDuration();
//		std::cout << index << " " << element->ToString() << std::endl;
	}
	time = 0.0;
	for ( int index = 0, count = lowerPartSoundElements.size(); index < count; index++ ) {
		const SoundElement32* element = lowerPartSoundElements[ index ];
		recorder.Record( 0, 0, time, element, 0.4, 0.6 );
		time += element->GetDuration();
//		std::cout << index << " " << element->ToString() << std::endl;
	}
	time = 0.0;
//	std::cout << bassNoteInfos.size() << std::endl;
	for ( int index = 0, count = bassPartSoundElements.size(); index < count; index++ ) {
		const SoundElement32* element = bassPartSoundElements[ index ];
		recorder.Record( 0, 0, time, element, 0.5, 0.5 );
		time += element->GetDuration();
//		std::cout << element << std::endl;
//		std::cout << element->ToString() << std::endl;
	}

	recorder.OutputAudioFile( path );

}
